package com.wu.framework.gateway.filter;

import com.wu.framework.gateway.config.SecurityUrlProperties;
import com.wu.framework.util.SecurityContextUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.time.Instant;

/**
 * description 网管拦截器
 *
 * @author 吴佳伟
 * @date 2021/5/25 2:10 下午
 */
@Slf4j
@Component
public class AuthenticationFilter extends ZuulFilter {

    private static final String UPMS_SERVICE_ID = "upms-provider";

    private static final String NO_LOGIN_MESSAGE = "{\"status\":false,\"message\":\"您还未登录，请登录后再进行操作！\",\"errorType\":0}";
    private static final String SESSION_EXPIRE_MESSAGE = "{\"status\":false,\"message\":\"会话已过期，请重新登录\",\"errorType\":0,\"data\":null}";
    private static final String PERMISSION_DENY_MESSAGE = "{\"status\":false,\"message\":\"您没有权限\",\"errorType\":0,\"data\":null}";
    private static final String INNER_PERMISSION_DENY_MESSAGE = "{\"status\":false,\"message\":\"无法通过网关访问内部接口\",\"errorType\":0,\"data\":null}";
    private static final String PERMISSION_URL_FETCH_ERROR_MESSAGE = "{\"status\":false,\"message\":\"获取受检权限失败，请检查upms服务是否正常\",\"errorType\":5,\"data\":null}";

    //    private final UpmsApi upmsApi;
    private final SecurityUrlProperties securityUrlProperties;
    private final RouteLocator routeLocator;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    public AuthenticationFilter(SecurityUrlProperties securityUrlProperties, RouteLocator routeLocator) {
        this.securityUrlProperties = securityUrlProperties;
        this.routeLocator = routeLocator;
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        String uri = request.getRequestURI();
        String method = request.getMethod().toLowerCase();

        //放行预检请求
        if (RequestMethod.OPTIONS.name().equals(method)) {
            return null;
        }

        if (antPathMatcher.match(securityUrlProperties.getInnerUri(), uri)) {
            if (!securityUrlProperties.getAllowInner()) {
                log.error("无法请求内部接口:{}", uri);
                responseError(requestContext, INNER_PERMISSION_DENY_MESSAGE);
                return null;
            } else {
                // CI环境中放行
                return null;
            }
        }

        //放行upms，由它自己进行权限验证
        Route matchingRoute = routeLocator.getMatchingRoute(request.getRequestURI());
        if (matchingRoute != null && UPMS_SERVICE_ID.equals(matchingRoute.getId())) {
            return null;
        }

        //放行内置免登url
        for (String ignoreUri : securityUrlProperties.getIgnores()) {
            if (antPathMatcher.match(ignoreUri, uri)) {
                return null;
            }
        }

        //放行额外免登url
        for (String ignoreUri : securityUrlProperties.getExternalIgnores()) {
            System.out.println(ignoreUri);
            if (antPathMatcher.match(ignoreUri, uri)) {
                return null;
            }
        }

        HttpSession session = request.getSession(false);
        if (session == null) {
            log.debug("未登录");
            responseError(requestContext, NO_LOGIN_MESSAGE);
        } else if (isExpired(session)) {
            log.debug("Session已过期");
            responseError(requestContext, SESSION_EXPIRE_MESSAGE);
        } else {
            Object userId = session.getAttribute(SecurityContextUtils.KEY_NAME_USER_ID);
            if (userId == null) {
                log.debug("未登录");
                responseError(requestContext, NO_LOGIN_MESSAGE);
                return null;
            }

            // 转发用户信息到http header中
            SecurityContextUtils.relayToZuulHeader(session, requestContext.getZuulRequestHeaders());

            //检查是否需要检查权限
//            List<String> requirePermissionUrls;
//            try {
//                requirePermissionUrls = upmsApi.listRequirePermissionUrlByMethod(method);
//            } catch (FeignException e) {
//                log.error("获取受检权限失败");
//                responseError(requestContext, PERMISSION_URL_FETCH_ERROR_MESSAGE);
//                return null;
//            }
//
//            assert matchingRoute != null;
//            uri = uri.replaceFirst(matchingRoute.getPrefix(), "");
//            if (CollectionUtils.isEmpty(requirePermissionUrls) || !requirePermissionUrls.contains(uri)) {
//                return null;
//            }
//
//            //检查是否拥有权限
//            SimpleGrantedAuthority requireAuthority = new SimpleGrantedAuthority(method + ":" + uri);
//            List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) session.getAttribute(SecurityContextUtils.KEY_NAME_AUTHORITIES);
//            if (authorities.contains(requireAuthority)) {
//                return null;
//            }

            log.debug("没有访问权限");
            responseError(requestContext, PERMISSION_DENY_MESSAGE);
        }

        return null;
    }

    private boolean isExpired(HttpSession session) {
        if (session.getMaxInactiveInterval() < 0) {
            return false;
        }
        return Instant.now().minus(Duration.ofSeconds(session.getMaxInactiveInterval()))
                .compareTo(Instant.ofEpochSecond(session.getLastAccessedTime())) >= 0;
    }

    private void responseError(RequestContext requestContext, String errorMessage) {
        requestContext.setSendZuulResponse(false);
        requestContext.addZuulResponseHeader("Content-Type", "application/json;charset=UTF-8");
        requestContext.setResponseStatusCode(401);
        requestContext.setResponseBody(errorMessage);
    }
}
